#include "sd_script.h"
#include "sd_server.h"
#include "sd_memory.h"
#include "sd_log.h"
#include <pthread.h>


extern void PrintIdxState(IdxState* idx, FILE* fp);

typedef struct sd_ScriptArg
{
	char szScriptFileName[512];
	char szOutputFileName[512];
	bool bMainThread;
}sd_ScriptArg;

static void* RunScript(void* arg);
static bool  ReadScriptLine(FILE* fp, char* szLine);
static const char* GetNextToken(const char* str, char* token);
static const char* ReadKey(const char* szCommandLine, sd_Key* key);
static const char* ReadRecord(const char* szCommandLine, sd_Record* record);
static void  OutputRecord(FILE* fp, const sd_Record* record);

sd_Ret sd_scriptRun(int argc, char* argv[])
{
	int i;
	int nNumThreads;
	pthread_t* threads = NULL;
	sd_ScriptArg* scArgs = NULL;
	sd_ScriptArg  initArgs;

	if (argc < 2)
	{
		sd_log_err("No input script!\n");
		return SD_RET_OK;
	}

	/* First, execute the initialization script */
	if (strcmp(argv[1], "NULL") != 0)
	{
		strcpy(initArgs.szScriptFileName, argv[1]);
		strcpy(initArgs.szOutputFileName, "script_out_init.txt");
		initArgs.bMainThread = true;
		RunScript((void*)&initArgs);
	}

	/* Concurrently execute the rest scripts */
	nNumThreads = argc-2;
	if (nNumThreads > 0)
	{
		void* pret;
		int err;
		scArgs = (sd_ScriptArg*)sd_malloc(sizeof(sd_ScriptArg)*nNumThreads);
		for (i=0;i<nNumThreads; i++)
		{
			strcpy(scArgs[i].szScriptFileName, argv[i+2]);
			sprintf(scArgs[i].szOutputFileName, "script_out_%d.txt",i);
			scArgs[i].bMainThread = false;
		}

		threads = (pthread_t*)sd_malloc(sizeof(pthread_t)*nNumThreads);
		for(i=0; i<nNumThreads; i++)
		{
			err = pthread_create(&threads[i], NULL, RunScript, &scArgs[i]);
			if (err != 0)
			{
				SD_ASSERT(false);
				return SD_RET_ERROR;
			}
		}

		/* Wait for all the scripted to be finished */
		for (i=0; i<nNumThreads; i++)
		{
			pthread_join(threads[i], &pret);
		}
		
		sd_free(threads);
		sd_free(scArgs);
		threads = NULL;
		scArgs = NULL;
	}
	return SD_RET_OK;
}

static void* RunScript(void* arg)
{
	char szCommandLine[512];
	char szCommand[64];
	const char* pCommand;
	FILE* script_fp = NULL;
	FILE* out_fp = NULL;
	IdxState* idxState = NULL;
	TxnState* txnState = NULL;
	sd_ScriptArg* scArg = NULL;
	
	scArg = (sd_ScriptArg*)arg;
	
	/* Open the script file */
	script_fp = fopen(scArg->szScriptFileName, "r");
	if (script_fp == NULL)
	{
		sd_log_err("Cannot open the script file : %s\n",scArg->szScriptFileName);
		if (scArg->bMainThread)
			return (void*)NULL;
		else
			pthread_exit(NULL);
	}

	/* Open the output file */
	out_fp = fopen(scArg->szOutputFileName, "w");
	if (out_fp == NULL)
	{
		fclose(script_fp);
		sd_log_err("Cannot open the output file : %s\n",scArg->szOutputFileName);
		if (scArg->bMainThread)
			return (void*)NULL;
		else
			pthread_exit(NULL);
	}

	/* Parse and execute each command */
	while(ReadScriptLine(script_fp, szCommandLine))
	{
		pCommand = szCommandLine;
		pCommand = GetNextToken(pCommand, szCommand);
		sd_log(szCommandLine);
		sd_log("\n");
		fprintf(out_fp,"\"%s\":\t",szCommandLine);

		/* Parse the command */
		if (stricmp(szCommand, "print") == 0)
		{
			fprintf(out_fp,"=========================================================\n");
			PrintIdxState(idxState, out_fp);
			fprintf(out_fp,"=========================================================\n");
		}
		else if (stricmp(szCommand, "insert") == 0)
		{
			ErrCode ret;
			sd_Record record;
			ReadRecord(pCommand, &record);
			ret = insertRecord(idxState, txnState, (Key*)&record.key, record.payload);
			SD_ASSERT(ret == SUCCESS);
		}
		else if (stricmp(szCommand, "delete") == 0)
		{
			ErrCode ret;
			sd_Record record;
			ReadRecord(pCommand, &record);
			ret = deleteRecord(idxState, txnState, (Record*)&record);
			if (ret == KEY_NOTFOUND)
			{
				fprintf(out_fp,"%s: KEY_NOTFOUND\n", szCommandLine);
			}
		}
		else if (stricmp(szCommand, "get") == 0)
		{
			ErrCode ret;
			sd_Record record;
			ReadKey(pCommand, &record.key);
			ret = get(idxState, txnState, (Record*)&record);
			if (ret == KEY_NOTFOUND)
			{
				fprintf(out_fp,"KEY_NOTFOUND");
			}
			else
			{
				OutputRecord(out_fp, &record);
			}
		}
		else if (stricmp(szCommand, "getnext") == 0)
		{
			ErrCode ret;
			sd_Record record;
			ret = getNext(idxState, txnState, (Record*)&record);
			if (ret == DB_END)
			{
				fprintf(out_fp,"DB_END");
			}
			else
			{
				OutputRecord(out_fp, &record);
			}

		}
		else if (stricmp(szCommand, "begintransaction") == 0)
		{
			beginTransaction(&txnState);
		}
		else if (stricmp(szCommand, "committransaction") == 0)
		{
			commitTransaction(txnState);
			txnState = NULL;
		}
		else if (stricmp(szCommand, "aborttransaction") == 0)
		{
			abortTransaction(txnState);
			txnState = NULL;
		}
		else if (stricmp(szCommand, "open") == 0)
		{
			char szIdxName[64];
			pCommand = GetNextToken(pCommand, szIdxName);
			openIndex(szIdxName, &idxState);
		}
		else if (stricmp(szCommand, "close") == 0)
		{
			char szIdxName[64];
			pCommand = GetNextToken(pCommand, szIdxName);
			closeIndex(idxState);
			idxState = NULL;
		}
		else if (stricmp(szCommand, "create") == 0)
		{
			char name[64];
			GetNextToken(pCommand, name);
			create(KEYTYPE_SHORT, name);
		}
		else
		{
			sd_log_err("Unknown command : %s in script file : %s \n");
		}
		fprintf(out_fp,"\n");
	}

	if (txnState != NULL)
	{
		commitTransaction(txnState);
		txnState = NULL;
	}

	if (idxState != NULL)
	{
		closeIndex(idxState);
		idxState = NULL;
	}
	

	fclose(script_fp);
	fclose(out_fp);

	if (!scArg->bMainThread)
		pthread_exit(NULL);
	return (void*)NULL;
}

static bool ReadScriptLine(FILE* fp, char* szLine)
{
	int ch;
	int len = 0;
	if (feof(fp))
		return false;

	/* Jump across the blank lines */
	while((ch=fgetc(fp))!= EOF)
	{
		if( ch != '\n' && ch != '\r')
			break;
	}
	
	/* Pick up the characters of the non-blank line */
	while(ch != EOF)
	{
		if (ch == '\n' || ch == '\r')
		{
			*szLine = '\0';
			break;
		}
		*szLine++ = (char)ch;
		len++;
		ch = fgetc(fp);
	}

	if (*szLine != '\0')
		*szLine = '\0';
	if (len == 0)
		return false;
	else
		return true;
}

static const char* GetNextToken(const char* str, char* token)
{
	while(*str == ' ' || *str == '\t')
		str++;
	if (*str == 0)
		return str;
	while(*str != ' ' && *str != '\t')
	{
		*token++ = *str++;
		if (*str == 0)
			break;
	}
	*token = 0;
	return str;
}

static const char* ReadRecord(const char* szCommandLine, sd_Record* record)
{
	const char* pCom = szCommandLine;
	
	pCom = ReadKey(szCommandLine, &record->key);
	pCom = GetNextToken(pCom, record->payload);
	if (strcmp(record->payload, "NULL") == 0)
		record->payload[0]=0;
	return pCom;
}

static const char* ReadKey(const char* szCommandLine, sd_Key* key)
{
	char token[64];
	const char* pCom = szCommandLine;
	pCom = GetNextToken(pCom, token);
	key->keyval.shortkey = atoi(token);
	key->type = SD_KEYTYPE_SHORT;
	return pCom;
}

static void OutputRecord(FILE* fp, const sd_Record* record)
{
	fprintf(fp, "%ld ", record->key.keyval.shortkey);
	if (record->payload[0] == 0)
		fprintf(fp, "NULL");
	else
		fprintf(fp, record->payload);
}



